#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../commun/asmfile.h"

// simule l'exécution d'un programme pour la machine orientée mémoire.

FILE* gFile;
int giLine = 0;

unsigned char mem[256];



void jump(int a)
{
   if (a >= 0)
      fseek(gFile, a * BMM_LINE_LEN, SEEK_SET); 
	if (a < 0 || ftell(gFile) != (a * BMM_LINE_LEN)) {
		fprintf(stderr, "IP@%d> Tried to jump to %d, which is out out of program\n",
		        giLine, a);
		exit(-1);
	}
   giLine = a-1;
}

#define OP(code, action)	case code: action; break;

void operation(int opAddr, int a1, int a2, int a3)
{
   int op = opAddr & 0x0F;
   
   // variables de pile
   if ((opAddr & ADDR_A) && op != JMP)
      a1 += mem[0];
   if ((opAddr & ADDR_B) && !(op == AFC || op == JMF))
      a2 += mem[0];
   if (opAddr & ADDR_C)
      a3 += mem[0];
   
   // sauts relatifs
   if (op == JMP && opAddr & ADDR_A)
      a1 = (a1 + giLine) % 256;
   if (op == JMF && opAddr & ADDR_B)
      a2 = (a2 + giLine) % 256;
   
   // contrôle pile
   if (a1 >= 256 || a2 >= 256 || a3 >= 256) {
      fprintf(stderr, "IP@%d> Stack Overflow (stack base is at: %d)\n", giLine, mem[0]);
      exit(-1);
   }
   
   //printf("IP: %d OP %d %d %d %d\n", giLine, op, a1, a2, a3);
   
	switch (op)
	{
	OP(ADD, mem[a1] = mem[a2] + mem[a3] );
	OP(MUL, mem[a1] = mem[a2] * mem[a3] );
	OP(SOU, mem[a1] = mem[a2] - mem[a3] );
	OP(DIV, if (mem[a3]) {mem[a1] = mem[a2] / mem[a3];}
           else {fprintf(stderr, "IP@%d> Division by Zero\n", giLine); exit(-1);});
	OP(COP, mem[a1] = mem[a2]           );
	OP(AFC, mem[a1] = a2                );
	OP(JMP, jump(a1)                    );
	OP(JMF, if (!mem[a1]) jump(a2)      );
	OP(INF, mem[a1] = mem[a2] < mem[a3] );
	OP(SUP, mem[a1] = mem[a2] > mem[a3] );
	OP(EQU, mem[a1] = mem[a2] == mem[a3]);
	OP(PRI, printf("%d\n", mem[a1])     );
	OP(STI, mem[mem[a2]] = mem[a1]      );
	OP(LDI, mem[a1] = mem[mem[a2]]      );
	OP(CPI, mem[mem[a1]] = mem[mem[a2]] );
	default:
		fprintf(stderr, "IP@%d> Unknown opcode: %d\n", giLine, op);
		exit(-1);
	}
}


#define CLEN(a) (sizeof(a) / sizeof(a[0]))
#define ERR(i) { fprintf(stderr, "line %d is invalid\n", (i)); return; }

void execute()
{
	while (1)
	{
		unsigned char line[BMM_LINE_LEN];
		int r;
		
		r = fread(line, 1, BMM_LINE_LEN, gFile);
		if (r == 0)
			break; // finished execution without errors
		if (r != BMM_LINE_LEN)
			ERR(giLine);
		
		operation(line[0], line[1], line[2], line[3]);
		
		giLine++;
	}
}



void usage()
{
	fprintf(stderr, "usage: simule <asmfile.bmm>\n");
	exit(-1);
}

int main(int argc, char** argv)
{
	if (argc != 2)
		usage();
	
	gFile = fopen(argv[1], "rb");
	if (!gFile) {
		perror("Can't open input file :");
		exit(-1);
	}
	
	execute();
	
	fclose(gFile);
}
